﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Zaretto.PasswordGenerator
{
    /// <summary>
    /// Random Password Generator, see http://www.multicians.org/thvv/gpw.html
    //  This password generator gives you a list of "pronounceable" passwords. It is modeled after Morrie Gasser's original generator described in
    //           Gasser, M., A Random Word Generator for Pronouncable Passwords, MTR-3006, 
    //            The MITRE Corporation, Bedford, MA 01730, ESD-TR-75-97, HQ Electronic Systems Division, Hanscom AFB, MA 01731. NTIS AD A 017676.
    //  except that Morrie's used a second-order approximation to English and this generator uses a third-order approximation. 
    // A descendant of Gasser's generator was added to the Multics operating system by Project Guardian in the mid 70s, and I believe Digital's VMS 
    // added a similar feature in the 80s.
    // FIPS Standard 181 describes a similar digraph-based generator, derived from Gasser's.
    // The first digraph-based password generator I know of was written by Daniel J. Edwards about 1965 for MIT's CTSS 
    // timesharing system. Over the years I have implemented versions in Multics PL/I, Tandem TAL, C++, Java, and JavaScript.
    // C# version by Richard Hazrrison : http://chateau-logic.com http://zaretto.com
    /// </summary>
    public class Creator
    {
        static String alphabet = "abcdefghijklmnopqrstuvwxyz";
        static Random rng = new Random();

        /// <summary>
        /// used when required to generate passwords starting from a specific seed.
        /// </summary>
        /// <param name="Seed"></param>
        public static void SetSeed(int Seed)
        {
            rng = new Random(Seed);
        }

        /// <summary>
        /// create a prounouncable word of the required length using third-order approximation.
        /// </summary>
        /// <param name="RequiredLength"></param>
        /// <returns></returns>
        static public string Generate(int RequiredLength)
        {
            int c1, c2, c3;
            long sum = 0;
            long WeightedRandom;
            StringBuilder password;
            var data = new GpwData();

            password = new StringBuilder(RequiredLength);
            WeightedRandom = (long)(rng.NextDouble() * data.Sigma); // weight by sum of frequencies
            sum = 0;

            bool finished = false;

            for (c1 = 0; c1 < 26 && !finished; c1++)
            {
                for (c2 = 0; c2 < 26 && !finished; c2++)
                {
                    for (c3 = 0; c3 < 26 && !finished; c3++)
                    {
                        sum += data.get(c1, c2, c3);
                        if (sum > WeightedRandom)
                        {
                            password.Append(alphabet[c1]);
                            password.Append(alphabet[c2]);
                            password.Append(alphabet[c3]);
                            finished = true;
                        }
                    }
                }
            }

            // Now do a random walk - starting at the 4th position as just done 3 above.
            int nchar = 3;
            while (nchar < RequiredLength)
            {
                c1 = alphabet.IndexOf(password[nchar - 2]);
                c2 = alphabet.IndexOf(password[nchar - 1]);

                sum = 0;

                for (c3 = 0; c3 < 26; c3++)
                    sum += data.get(c1, c2, c3);

                if (sum == 0)
                    break;

                WeightedRandom = (long)(rng.NextDouble() * sum);

                sum = 0;

                for (c3 = 0; c3 < 26; c3++)
                {
                    sum += data.get(c1, c2, c3);
                    if (sum > WeightedRandom)
                    {
                        password.Append(alphabet[c3]);
                        break;
                    }
                }
                nchar++;
            }
            return password.ToString();
        }
        
        /// <summary>
        /// generate a pass phrase built from pronouncable words
        /// </summary>
        /// <param name="minLength"></param>
        /// <param name="minWordLength"></param>
        /// <param name="maxWordLength"></param>
        /// <returns></returns>
        static public string GeneratePhrase(int minLength, int minWordLength = 3, int maxWordLength = 6)
        {
            var words = new List<string>();
            var passwordLength = 0;

            while (passwordLength < minLength)
            {
                var length = rng.Next(maxWordLength - minWordLength + 1) + minWordLength;
                var word = Generate(length);
                passwordLength += word.Length;
                words.Add(word);
            }
            return string.Join(" ", words.ToArray());
        }
    }
}
